import os
import re
import shutil
import plistlib
from pathlib import Path
from typing import Dict, Optional

from core.cloners_module.base_cloner import BaseProjectCloner


class IosProjectCloner(BaseProjectCloner):
    """负责 iOS 项目的克隆逻辑"""

    def clone(
        self,
        source_path: str,
        target_path: str,
        project_info: Dict,
        new_bundle_id: Optional[str],
        new_project_name: Optional[str],
        modify_fingerprints: bool,
    ) -> str:
        """
        克隆 iOS 项目
        :param source_path: 源项目路径
        :param target_path: 目标项目路径
        :param project_info: 项目信息字典
        :param new_bundle_id: 新的 Bundle ID，如果为 None 则自动生成
        :param new_project_name: 新的项目名称，如果为 None 则自动生成
        :param modify_fingerprints: 是否修改文件指纹
        :return: 目标项目路径
        """
        self.logger.log_operation("克隆iOS项目", "开始复制文件")

        original_bundle_id = self._get_ios_bundle_id(project_info)
        original_project_name = self._get_ios_project_name(source_path, project_info)

        if not original_bundle_id:
            self.logger.log_warning("未能自动识别原始 Bundle ID，请提供 new_bundle_id 参数以完成更新。")
            if not new_bundle_id:
                raise ValueError("无法确定原始 Bundle ID，请手动提供 new_bundle_id。")
        if original_bundle_id and not new_bundle_id:
            new_bundle_id = f"{original_bundle_id}.clone"

        if not new_project_name:
            new_project_name = f"{original_project_name}Clone"

        self._copy_project_files(source_path, target_path, "ios")

        if new_bundle_id and (not original_bundle_id or new_bundle_id != original_bundle_id):
            self._update_ios_bundle_id(target_path, original_bundle_id, new_bundle_id)

        if new_project_name != original_project_name:
            self._update_ios_project_name(target_path, original_project_name, new_project_name)

        self._modify_fingerprints_if_needed(target_path, "ios", modify_fingerprints)

        self._update_ios_project_configs(target_path, project_info, original_project_name, new_project_name)
        self._update_info_plist_display_names(target_path, original_project_name, new_project_name)
        self._cleanup_and_update_pods(target_path, original_project_name, new_project_name)
        self._check_third_party_configs(target_path)

        self.logger.log_operation(
            "iOS项目克隆完成",
            f"新Bundle ID: {new_bundle_id}, 新项目名: {new_project_name}",
        )

        return target_path

    def _get_ios_bundle_id(self, project_info: Dict) -> Optional[str]:
        """
        尝试从 Info.plist 或 Xcode 项目文件中提取 Bundle ID
        """
        for plist_path in project_info["structure"].get("info_plist", []):
            try:
                with open(plist_path, "rb") as handle:
                    plist = plistlib.load(handle)
                bundle_id = plist.get("CFBundleIdentifier", "")
                if bundle_id and not bundle_id.startswith("$("):
                    return bundle_id
            except Exception:  # noqa: BLE001
                continue

        xcodeproj = project_info["structure"].get("xcodeproj")
        if xcodeproj:
            pbxproj_path = os.path.join(xcodeproj, "project.pbxproj")
            try:
                with open(pbxproj_path, "r", encoding="utf-8") as handle:
                    content = handle.read()
                match = re.search(r"PRODUCT_BUNDLE_IDENTIFIER\s*=\s*([^;]+);", content)
                if match:
                    bundle_id = match.group(1).strip().strip('"')
                    if bundle_id and not bundle_id.startswith("$("):
                        self.logger.log_operation("检测Bundle ID", f"从pbxproj读取: {bundle_id}")
                        return bundle_id
            except Exception:  # noqa: BLE001
                pass

        return None

    def _get_ios_project_name(self, project_path: str, project_info: Dict) -> str:
        """
        尝试从 .xcodeproj 文件夹名称中提取项目名称
        """
        xcodeproj = project_info["structure"].get("xcodeproj")
        if xcodeproj:
            return Path(xcodeproj).stem
        return Path(project_path).name

    def _update_ios_bundle_id(self, project_path: str, old_id: Optional[str], new_id: str) -> None:
        """
        更新项目中的 Bundle ID
        包括 Info.plist 文件和 Xcode 项目文件中的配置
        """
        self.logger.log_operation("更新Bundle ID", f"{old_id if old_id else '未知'} -> {new_id}")

        plist_updated = False
        for root, dirs, files in os.walk(project_path):
            dirs[:] = [d for d in dirs if d not in ["Pods", "Carthage", "build", "DerivedData"]]

            for file in files:
                if file != "Info.plist":
                    continue
                plist_path = os.path.join(root, file)
                try:
                    with open(plist_path, "rb") as handle:
                        plist = plistlib.load(handle)

                    updated = False
                    if old_id and "CFBundleIdentifier" in plist:
                        bundle_id_value = plist["CFBundleIdentifier"]
                        if bundle_id_value == old_id:
                            plist["CFBundleIdentifier"] = new_id
                            updated = True
                        elif old_id in str(bundle_id_value):
                            plist["CFBundleIdentifier"] = str(bundle_id_value).replace(old_id, new_id)
                            updated = True

                    if updated:
                        with open(plist_path, "wb") as handle:
                            plistlib.dump(plist, handle)
                        self.logger.log_file_processing(plist_path, f"更新Bundle ID: {new_id}")
                        plist_updated = True
                except Exception as exc:  # noqa: BLE001
                    self.logger.log_warning(f"更新Info.plist失败 {plist_path}: {str(exc)}")

        if old_id and not plist_updated:
            self.logger.log_warning("未找到包含Bundle ID的Info.plist文件")

        # 更新 Xcode 项目设置
        self._update_xcode_project_settings(project_path, old_id, new_id)

    def _update_xcode_project_settings(
        self,
        project_path: str,
        old_bundle_id: Optional[str],
        new_bundle_id: str,
    ) -> None:
        """
        更新 Xcode 项目文件中的 Bundle ID 配置
        """
        for root, dirs, files in os.walk(project_path):
            if "project.pbxproj" not in files:
                continue
            pbxproj_path = os.path.join(root, "project.pbxproj")
            try:
                with open(pbxproj_path, "r", encoding="utf-8") as handle:
                    content = handle.read()

                original_content = content
                pattern = re.compile(
                    r"(PRODUCT_BUNDLE_IDENTIFIER\s*=\s*)([^;]+)(;)",
                    flags=re.MULTILINE,
                )

                def _replace_identifier(match: re.Match) -> str:
                    prefix, raw_value, suffix = match.groups()
                    value_stripped = raw_value.strip()

                    has_quotes = value_stripped.startswith('"') and value_stripped.endswith('"')
                    normalized_value = value_stripped.strip('"')

                    if normalized_value.startswith("$("):
                        return match.group(0)

                    if not old_bundle_id:
                        return match.group(0)

                    if normalized_value == old_bundle_id:
                        replacement_core = new_bundle_id
                    elif normalized_value.startswith(f"{old_bundle_id}."):
                        replacement_core = f"{new_bundle_id}{normalized_value[len(old_bundle_id):]}"
                    else:
                        return match.group(0)

                    replacement_value = f'"{replacement_core}"' if has_quotes else replacement_core

                    leading_ws_len = len(raw_value) - len(raw_value.lstrip())
                    trailing_ws_len = len(raw_value) - len(raw_value.rstrip())
                    leading_ws = raw_value[:leading_ws_len]
                    trailing_ws = raw_value[len(raw_value) - trailing_ws_len :] if trailing_ws_len else ""

                    return f"{prefix}{leading_ws}{replacement_value}{trailing_ws}{suffix}"

                content = pattern.sub(_replace_identifier, content)

                if content != original_content:
                    with open(pbxproj_path, "w", encoding="utf-8") as handle:
                        handle.write(content)
                    self.logger.log_file_processing(pbxproj_path, f"更新Bundle ID配置: {new_bundle_id}")
                else:
                    self.logger.log_warning("未在pbxproj中找到 PRODUCT_BUNDLE_IDENTIFIER 配置")
            except Exception as exc:  # noqa: BLE001
                self.logger.log_warning(f"更新pbxproj Bundle ID失败: {str(exc)}")

    def _update_ios_project_name(self, project_path: str, old_name: str, new_name: str) -> None:
        """
        更新项目中的名称引用
        包括 Xcode 项目文件、Info.plist 文件、Scheme 文件等
        """
        self.logger.log_operation("更新iOS项目名称", f"{old_name} -> {new_name}")

        old_source_dir = os.path.join(project_path, old_name)
        if os.path.exists(old_source_dir) and os.path.isdir(old_source_dir):
            new_source_dir = os.path.join(project_path, new_name)
            os.rename(old_source_dir, new_source_dir)
            self.logger.log_operation("重命名源码目录", f"{old_name}/ -> {new_name}/")

        for item in os.listdir(project_path):
            if item.endswith(".xcodeproj") and old_name in item:
                old_proj = os.path.join(project_path, item)
                new_proj = os.path.join(project_path, item.replace(old_name, new_name))
                os.rename(old_proj, new_proj)
                self.logger.log_operation("重命名.xcodeproj", f"{item} -> {item.replace(old_name, new_name)}")

        for item in os.listdir(project_path):
            if item.endswith(".xcworkspace") and old_name in item:
                old_workspace = os.path.join(project_path, item)
                new_workspace = os.path.join(project_path, item.replace(old_name, new_name))
                os.rename(old_workspace, new_workspace)
                self.logger.log_operation("重命名.xcworkspace", f"{item} -> {item.replace(old_name, new_name)}")

        for root, dirs, files in os.walk(project_path):
            if "project.pbxproj" in files:
                pbxproj_path = os.path.join(root, "project.pbxproj")
                self._update_pbxproj_names(pbxproj_path, old_name, new_name)
                self._update_test_and_extension_targets(pbxproj_path, old_name, new_name)

        for root, dirs, files in os.walk(project_path):
            dirs[:] = [d for d in dirs if d not in ["Pods", "Carthage", "build", "DerivedData"]]
            for file in files:
                file_path = os.path.join(root, file)
                if file == f"{old_name}-Bridging-Header.h":
                    os.rename(file_path, os.path.join(root, f"{new_name}-Bridging-Header.h"))
                    self.logger.log_operation("重命名Bridging Header", f"{file} -> {new_name}-Bridging-Header.h")
                elif file == f"{old_name}.entitlements":
                    os.rename(file_path, os.path.join(root, f"{new_name}.entitlements"))
                    self.logger.log_operation("重命名entitlements", f"{file} -> {new_name}.entitlements")

        for root, dirs, files in os.walk(project_path):
            for file in files:
                if not file.endswith(".xcscheme"):
                    continue
                scheme_path = os.path.join(root, file)
                self._update_xcscheme_names(scheme_path, old_name, new_name)
                if file == f"{old_name}.xcscheme":
                    os.rename(scheme_path, os.path.join(root, f"{new_name}.xcscheme"))
                    self.logger.log_operation("重命名scheme文件", f"{file} -> {new_name}.xcscheme")

        text_extensions = {".plist", ".strings", ".pbxproj", ".xcscheme", ".xcworkspacedata"}
        for root, dirs, files in os.walk(project_path):
            dirs[:] = [d for d in dirs if d not in ["Pods", "Carthage", "build", "DerivedData"]]
            for file in files:
                _, ext = os.path.splitext(file.lower())
                if ext not in text_extensions:
                    continue
                file_path = os.path.join(root, file)
                try:
                    with open(file_path, "r", encoding="utf-8") as handle:
                        content = handle.read()
                    if old_name in content:
                        content = content.replace(old_name, new_name)
                        with open(file_path, "w", encoding="utf-8") as handle:
                            handle.write(content)
                        self.logger.log_file_processing(file_path, f"更新项目名引用: {old_name} -> {new_name}")
                except Exception as exc:  # noqa: BLE001
                    self.logger.log_warning(f"更新文件失败 {file_path}: {str(exc)}")

    def _update_pbxproj_names(self, pbxproj_path: str, old_name: str, new_name: str) -> None:
        """
        更新 Xcode 项目文件中的项目名称引用
        """
        try:
            with open(pbxproj_path, "r", encoding="utf-8") as handle:
                content = handle.read()

            original_content = content

            content = re.sub(
                rf'(name\s*=\s*"){re.escape(old_name)}(";)',
                rf"\1{new_name}\2",
                content,
            )

            content = re.sub(
                rf'(PRODUCT_NAME\s*=\s*"){re.escape(old_name)}(";)',
                rf"\1{new_name}\2",
                content,
            )

            content = re.sub(
                rf"(/\*\s*){re.escape(old_name)}(\s*\*/)",
                rf"\1{new_name}\2",
                content,
            )

            content = re.sub(
                rf'(path\s*=\s*"){re.escape(old_name)}(";)',
                rf"\1{new_name}\2",
                content,
            )
            content = re.sub(
                rf'(path\s*=\s*){re.escape(old_name)}(;)',
                rf"\1{new_name}\2",
                content,
            )

            content = re.sub(
                rf'"{re.escape(old_name)}/',
                f'"{new_name}/',
                content,
            )
            content = re.sub(
                rf"\s{re.escape(old_name)}/",
                f" {new_name}/",
                content,
            )

            content = re.sub(
                rf"{re.escape(old_name)}-Bridging-Header",
                f"{new_name}-Bridging-Header",
                content,
            )

            content = re.sub(
                rf"{re.escape(old_name)}\.app",
                f"{new_name}.app",
                content,
            )

            content = re.sub(
                rf"{re.escape(old_name)}\.entitlements",
                f"{new_name}.entitlements",
                content,
            )

            content = re.sub(
                rf"Pods_{re.escape(old_name)}",
                f"Pods_{new_name}",
                content,
            )

            content = re.sub(
                rf"Pods-{re.escape(old_name)}",
                f"Pods-{new_name}",
                content,
            )

            content = re.sub(
                rf"(\$\(SRCROOT\)/{re.escape(old_name)})",
                rf"$(SRCROOT)/{new_name}",
                content,
            )

            if content != original_content:
                with open(pbxproj_path, "w", encoding="utf-8") as handle:
                    handle.write(content)
                self.logger.log_file_processing(pbxproj_path, f"更新项目引用: {old_name} -> {new_name}")
            else:
                self.logger.log_warning(f"pbxproj中未找到需要更新的引用: {old_name}")
        except Exception as exc:  # noqa: BLE001
            self.logger.log_warning(f"更新pbxproj失败 {pbxproj_path}: {str(exc)}")

    def _update_xcscheme_names(self, scheme_path: str, old_name: str, new_name: str) -> None:
        """
        更新 Xcode Scheme 文件中的项目名称引用
        """
        try:
            with open(scheme_path, "r", encoding="utf-8") as handle:
                content = handle.read()

            if old_name in content:
                content = content.replace(old_name, new_name)
                with open(scheme_path, "w", encoding="utf-8") as handle:
                    handle.write(content)
                self.logger.log_file_processing(scheme_path, f"更新scheme: {old_name} -> {new_name}")
        except Exception as exc:  # noqa: BLE001
            self.logger.log_warning(f"更新scheme失败 {scheme_path}: {str(exc)}")

    def _update_test_and_extension_targets(self, pbxproj_path: str, old_name: str, new_name: str) -> None:
        """
        更新 Xcode 项目文件中的测试和扩展 targets 名称引用
        """
        try:
            with open(pbxproj_path, "r", encoding="utf-8") as handle:
                content = handle.read()

            original_content = content
            test_patterns = [
                (f"{old_name}Tests", f"{new_name}Tests"),
                (f"{old_name}UITests", f"{new_name}UITests"),
                (f"{old_name} Tests", f"{new_name} Tests"),
                (f"{old_name} UI Tests", f"{new_name} UI Tests"),
            ]

            for old_pattern, new_pattern in test_patterns:
                content = content.replace(old_pattern, new_pattern)

            extension_suffixes = [
                "Widget",
                "NotificationService",
                "NotificationContent",
                "ShareExtension",
                "TodayExtension",
                "WatchApp",
                "WatchExtension",
                "IntentsExtension",
                "IntentsUIExtension",
            ]

            for suffix in extension_suffixes:
                content = content.replace(f"{old_name} {suffix}", f"{new_name} {suffix}")
                content = content.replace(f"{old_name}{suffix}", f"{new_name}{suffix}")
                content = content.replace(f"{old_name}-{suffix}", f"{new_name}-{suffix}")
                content = content.replace(f"{old_name}_{suffix}", f"{new_name}_{suffix}")

            if content != original_content:
                with open(pbxproj_path, "w", encoding="utf-8") as handle:
                    handle.write(content)
                self.logger.log_file_processing(pbxproj_path, "更新测试和扩展targets")
        except Exception as exc:  # noqa: BLE001
            self.logger.log_warning(f"更新测试和扩展targets失败: {str(exc)}")

    def _update_info_plist_display_names(self, project_path: str, old_name: str, new_name: str) -> None:
        """
        更新 Info.plist 文件中的显示名称
        """
        for root, dirs, files in os.walk(project_path):
            dirs[:] = [d for d in dirs if d not in ["Pods", "Carthage", "build", "DerivedData"]]
            for file in files:
                if file != "Info.plist":
                    continue
                plist_path = os.path.join(root, file)
                try:
                    with open(plist_path, "rb") as handle:
                        plist = plistlib.load(handle)

                    updated = False
                    if plist.get("CFBundleDisplayName") == old_name:
                        plist["CFBundleDisplayName"] = new_name
                        updated = True

                    if plist.get("CFBundleName") == old_name:
                        plist["CFBundleName"] = new_name
                        updated = True

                    if updated:
                        with open(plist_path, "wb") as handle:
                            plistlib.dump(plist, handle)
                        self.logger.log_file_processing(plist_path, f"更新显示名称: {new_name}")
                except Exception as exc:  # noqa: BLE001
                    self.logger.log_warning(f"更新Info.plist失败 {plist_path}: {str(exc)}")

    def _cleanup_and_update_pods(self, project_path: str, old_name: str, new_name: str) -> None:
        """
        清理并更新 CocoaPods 相关配置
        包括更新 Podfile 中的 target 名称，删除 Pods 目录和 Podfile.lock 文件
        """
        self.logger.log_operation("处理 CocoaPods 配置", "开始清理和更新")

        podfile_path = os.path.join(project_path, "Podfile")
        if os.path.exists(podfile_path):
            try:
                with open(podfile_path, "r", encoding="utf-8") as handle:
                    content = handle.read()

                content = re.sub(
                    rf"target\s+['\"]({re.escape(old_name)})['\"]",
                    f"target '{new_name}'",
                    content,
                )

                with open(podfile_path, "w", encoding="utf-8") as handle:
                    handle.write(content)

                self.logger.log_file_processing(podfile_path, f"更新target: {old_name} -> {new_name}")
            except Exception as exc:  # noqa: BLE001
                self.logger.log_warning(f"更新Podfile失败: {str(exc)}")

        pods_dir = os.path.join(project_path, "Pods")
        if os.path.exists(pods_dir):
            try:
                shutil.rmtree(pods_dir)
                self.logger.log_operation("删除Pods目录", "已删除，需要重新运行 pod install")
            except Exception as exc:  # noqa: BLE001
                self.logger.log_warning(f"删除Pods目录失败: {str(exc)}")

        podfile_lock = os.path.join(project_path, "Podfile.lock")
        if os.path.exists(podfile_lock):
            try:
                os.remove(podfile_lock)
                self.logger.log_operation("删除Podfile.lock", "已删除")
            except Exception as exc:  # noqa: BLE001
                self.logger.log_warning(f"删除Podfile.lock失败: {str(exc)}")

        for item in os.listdir(project_path):
            if not item.endswith(".xcworkspace"):
                continue
            workspace_path = os.path.join(project_path, item)
            xcuserdata = os.path.join(workspace_path, "xcuserdata")
            if os.path.exists(xcuserdata):
                try:
                    shutil.rmtree(xcuserdata)
                    self.logger.log_operation("清理xcuserdata", f"从 {item} 删除")
                except Exception as exc:  # noqa: BLE001
                    self.logger.log_warning(f"清理xcuserdata失败: {str(exc)}")

        print("\n" + "=" * 60)
        print("⚠️  重要提示：CocoaPods 配置")
        print("=" * 60)
        print(f"克隆的项目已更新 Podfile target 名称为: {new_name}")
        print("已删除以下文件/目录，需要重新安装:")
        print("  • Pods/")
        print("  • Podfile.lock")
        print("\n请在克隆的项目目录中运行:")
        print(f"  cd {project_path}")
        print("  pod install")
        print("=" * 60 + "\n")

    def _update_ios_project_configs(
        self,
        project_path: str,
        project_info: Dict,
        old_name: str,
        new_name: str,
    ) -> None:
        """
        更新项目中的其他配置文件
        目前主要是更新 Firebase 的 GoogleService-Info.plist 文件
        """
        _ = (project_path, project_info, old_name, new_name)

    def _check_third_party_configs(self, project_path: str) -> None:
        """
        检查并提示第三方 SDK 的配置文件
        目前主要检查 Firebase 的 GoogleService-Info.plist 文件
        """
        warnings = []

        for root, dirs, files in os.walk(project_path):
            if "GoogleService-Info.plist" in files:
                firebase_plist = os.path.join(root, "GoogleService-Info.plist")
                warnings.append(
                    {
                        "type": "Firebase",
                        "file": firebase_plist,
                        "message": "需要替换为新项目的 Firebase 配置",
                    }
                )
                break

        third_party_files = [
            "appcenter-config.json",
            "fabric.apikey",
            "OneSignalNotificationServiceExtension-Info.plist",
        ]

        for root, dirs, files in os.walk(project_path):
            dirs[:] = [d for d in dirs if d not in ["Pods", "Carthage", "build"]]
            for file in files:
                if file in third_party_files:
                    warnings.append(
                        {
                            "type": "第三方SDK",
                            "file": os.path.join(root, file),
                            "message": "可能需要更新配置",
                        }
                    )

        if warnings:
            print("\n" + "=" * 60)
            print("⚠️  第三方SDK配置检查")
            print("=" * 60)
            for warning in warnings:
                print(f"\n{warning['type']}: {warning['file']}")
                print(f"  {warning['message']}")
            print("\n" + "=" * 60 + "\n")
